import { UploadFile } from 'antd';
import React, {
  createContext,
  forwardRef,
  useContext,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState
} from 'react';
import { message } from '../../functionalComponent';
import { iup, useRefCallback } from '../../util';
import { getTableAttachmentApi, openFormTableAttachment } from './api';
import { chunkData, ProgressData, UploadProps } from './components';
import { ITableAttachmentApi, ITableAttachmentProps } from './interface';
import { getTempAttachment, mergeChunks as mergeFileChunks, uploadFile } from './service';
import { getFileType } from './util';

const DEFAULT_ATTACHMENT_RECORD_LIST = [];
export const DEFAULT_ATTACHMENT_UPLOAD_MAX_COUNT = 99;

export interface IAttachmentContext {
  mergeChunks: (md5: string, totalChunks: number) => Promise<{ fileId?: string }>;
  upload: (data: chunkData) => Promise<void>;
  beforeUploadValidate: (file: File, fileList: File[]) => Promise<boolean>;
  accept?: string;
  maxCount: number;
  disabled?: boolean;
  uploadThreadCount: number;
  chunkSize: number;
  actions: {
    delete: (data: AttachmentFile[]) => Promise<boolean>;
    zipDownload: () => Promise<void>;
    isPreview: (name: string) => boolean;
    preview: (fileId: string) => Promise<void>;
    download: (data: any) => Promise<void>;
    openTableAttachment: (configs: any) => Promise<any>;
  };
  onContentChanged?: () => void;
  attachmentRecordList: Awaited<ReturnType<typeof getTempAttachment>>;
  loading: boolean;
  permission: ITableAttachmentProps['permission'];
  isReady?: boolean;
  uploading?: boolean;
  attachmentFileList: AttachmentFile[];
  onUploadStateChange: UploadProps['onUploadStateChange'];
  apiRef?: React.MutableRefObject<ITableAttachmentApi>;
  approved: 0 | 1;
}

export const AttachmentContext = createContext<IAttachmentContext>({
  mergeChunks: async () => ({ fileId: '' }),
  upload: async () => {},
  beforeUploadValidate: async () => true,
  maxCount: DEFAULT_ATTACHMENT_UPLOAD_MAX_COUNT,
  disabled: false,
  uploadThreadCount: 3,
  chunkSize: 5242880,
  actions: {
    delete: async () => false,
    zipDownload: async () => {},
    isPreview: () => false,
    preview: async () => {},
    download: async () => {},
    openTableAttachment: async () => {}
  },
  onContentChanged: async () => {},
  attachmentRecordList: [],
  loading: false,
  permission: {
    add: 1,
    delete: 1,
    edit: 1,
    view: 1,
    preview: 1,
    download: 1,
    zipDownload: 1,
    manage: 1
  },
  isReady: false,
  uploading: false,
  attachmentFileList: [],
  onUploadStateChange: () => {},
  approved: 0
});

export type AttachmentProviderProps = ITableAttachmentProps & {
  children: React.ReactNode;
  disabled?: boolean;
  value?: string;
  onChange?: (v: string) => void;
};

export type UploadedFile = IAttachmentContext['attachmentRecordList'][number] & {
  fileName: string;
  status: UploadFile['status'];
};

export type AttachmentFile = ProgressData | UploadedFile;

const useAttachmentRecordList = (
  attachmentRecordList: IAttachmentContext['attachmentRecordList'] | undefined = DEFAULT_ATTACHMENT_RECORD_LIST
) => {
  const uploadedFilesRef = useRef<Record<string, boolean>>({});
  const [upLoadingFileList, setUpLoadingFileList] = useState<ProgressData[]>([]);
  const [uploading, setUploading] = useState(false);

  const onUploadStateChange = useRefCallback<UploadProps['onUploadStateChange']>((state, _uploading) => {
    setUploading(_uploading);
    const { progress } = state;
    const list = Object.values(progress);
    setUpLoadingFileList(list);
    list.forEach((v) => {
      if (!uploadedFilesRef.current[v.fileName]) {
        uploadedFilesRef.current[v.fileName] = v.status === 'done';
      }
    });
  });

  const attachmentFileList: AttachmentFile[] = useMemo(() => {
    const names: string[] = [];
    const uploadedFileList: UploadedFile[] = attachmentRecordList.map((data) => {
      const uploaded = uploadedFilesRef.current[data.asrName];
      names.push(data.asrName);
      return {
        ...data,
        fileName: data.asrName,
        status: uploaded ? 'done' : undefined
      };
    });
    const list = upLoadingFileList.filter((file) => !names.includes(file.fileName) && file.status !== 'error');
    return [...list, ...uploadedFileList];
  }, [upLoadingFileList, attachmentRecordList]);

  const removeFiles = useRefCallback((fileNames: string[]) => {
    if (fileNames.length === 0) {
      return;
    }
    fileNames.forEach((name) => (uploadedFilesRef.current[name] = false));
    setUpLoadingFileList((list) => list.filter((v) => !fileNames.includes(v.fileName)));
  });

  const reset = useRefCallback(() => {
    uploadedFilesRef.current = {};
    setUpLoadingFileList([]);
    setUploading(false);
  });

  return {
    uploading,
    removeFiles,
    attachmentFileList,
    onUploadStateChange,
    reset
  };
};

export const AttachmentProvider = forwardRef<unknown, AttachmentProviderProps>(({ children, ...rest }, ref) => {
  const {
    disabled: propDisabled,
    busTypeCode: propBusTypeCode,
    uCode: propUCode,
    asrTable: propAsrTable,
    asrCode: propAsrCode,
    asrSessionGuid: propAsrSessionGuid,
    value,
    // eslint-disable-next-line react-hooks/rules-of-hooks
    apiRef = useRef<ITableAttachmentApi>({} as ITableAttachmentApi),
    onChange,
    permission: propsPermission,
    asrFill: propAsrFill,
    asrFillName: propAsrFillName,
    unitNo,
    orgNo,
    projectNo,
    asrAttachTable = '',
    busUrl,
    approved = 0,
    maxCount = DEFAULT_ATTACHMENT_UPLOAD_MAX_COUNT,
    uploadThreadCount = 3,
    chunkSize = 5242880,
    customOpenTab,
    onSave,
    fileTypeCode = '',
    accept,
    bizCode = iup.getPageInstance()?.busType
  } = rest;
  const disabled = propDisabled;
  const busTypeCode = propBusTypeCode || iup.getPageInstance()?.busType;
  const asrSessionGuid = propAsrSessionGuid || value || '';
  const asrTable = propAsrTable || value;
  const asrCode = propAsrCode || value;
  const innerRef = useRef<{
    loadPromise?: Promise<any>;
    isChanged: boolean;
    cancelId: string;
  }>({ isChanged: false, cancelId: iup.uniqueId('get_attachment_list') });

  const [attachmentRecordList, setAttachmentRecordList] = useState<IAttachmentContext['attachmentRecordList']>();
  const uploadingRef = useRef(false);

  const [loading, setLoading] = useState(false);

  const onChangeCallback = useRefCallback(() => {
    if (asrSessionGuid !== apiRef.current.asrSessionGuid && apiRef.current.asrSessionGuid && onChange) {
      onChange(apiRef.current.asrSessionGuid);
    }
  });

  const getTableAttach = useRefCallback(async (updateFlg = false) => {
    // eslint-disable-next-line no-async-promise-executor
    innerRef.current.loadPromise = new Promise(async (resolve, reject) => {
      try {
        setLoading(true);
        if (apiRef.current.asrSessionGuid) {
          apiRef.current = {
            ...apiRef.current,
            attachmentRecordList: await getTempAttachment({ cursorId: apiRef.current.asrSessionGuid })
          };
          if (updateFlg) {
            onChangeCallback();
          }
        } else {
          apiRef.current.asrSessionGuid = asrSessionGuid;
          const result = await getTableAttachmentApi(
            {
              asrSessionGuid,
              asrFill: propAsrFill,
              asrFillName: propAsrFillName,
              unitNo,
              orgNo,
              projectNo,
              uCode: propUCode,
              approved,
              asrTable,
              asrCode,
              asrAttachTable,
              busTypeCode,
              busUrl,
              customOpenTab,
              onSave,
              fileTypeCode,
              bizCode
            },
            innerRef.current.cancelId
          );
          apiRef.current = result;
        }
        setLoading(false);
        setAttachmentRecordList(apiRef.current.attachmentRecordList);
        resolve(apiRef.current);
        innerRef.current.loadPromise = undefined;
      } catch (error) {
        if ((error as any).message !== 'CancelTokenError') {
          setLoading(false);
          reject(error);
          innerRef.current.loadPromise = undefined;
        }
      }
    });
    await innerRef.current.loadPromise;
  });
  const { uploading, onUploadStateChange, attachmentFileList, removeFiles, reset } =
    useAttachmentRecordList(attachmentRecordList);
  uploadingRef.current = uploading;

  useEffect(() => {
    if (apiRef.current.asrSessionGuid === asrSessionGuid) {
      return;
    }
    apiRef.current.asrSessionGuid = '';
    // 外部 asrSessionGuid 发生变化，清空数据
    reset();
    getTableAttach();
  }, [asrSessionGuid, reset]);

  useImperativeHandle(
    ref,
    () => ({
      getApi: () => {
        return {
          ...apiRef.current,
          async getCount() {
            if (innerRef.current.loadPromise) {
              const result = await innerRef.current.loadPromise;
              return result?.attachmentRecordList?.length || 0;
            }
            return apiRef.current?.attachmentRecordList?.length || 0;
          },
          getResult() {
            return innerRef.current.loadPromise || apiRef.current;
          },
          isChanged() {
            return innerRef.current.isChanged;
          },
          uploadingRef
        };
      }
    }),
    []
  );

  const upload = useRefCallback((data: chunkData) => {
    return uploadFile({
      ...data,
      busiType: fileTypeCode,
      cursorId: apiRef.current.asrSessionGuid || '',
      bizCode,
      unitNo,
      // todo 以下选填
      ctlgId: ''
    }).then((res) => {
      if (res.code !== 0) {
        const err = new Error(res.message);
        err.name = 'api';
        throw err;
      }
      return res;
    });
  });

  const mergeChunks = useRefCallback((md5: string, totalChunks: number) => {
    return mergeFileChunks({
      md5,
      totalChunks,
      busiType: fileTypeCode,
      cursorId: apiRef.current.asrSessionGuid || ''
    });
  });

  const existedFileNames = useMemo(
    () =>
      attachmentFileList
        .filter((v) => {
          return v.status !== 'fail' && v.status !== 'error';
        })
        .map((item) => item.fileName),
    [attachmentFileList]
  );

  const beforeUploadValidate = useRefCallback(async (file: File, fileList: File[]) => {
    const suffix = getFileType(file.name);
    const fileText = `文件（${file.name}）的`;
    const { tableAttachInfo: attachInfo } = apiRef.current as ITableAttachmentApi;
    if (file.size === 0) {
      message.error(`文件 ${file.name} 无任何内容，无法上传`);
      return false;
    }
    if (attachInfo?.specialChars) {
      for (const str of attachInfo?.specialChars) {
        if (file.name.includes(str)) {
          message.error(fileText + '文件名包含特殊符号，无法上传');
          return false;
        }
      }
    }

    const fileWhiteList = attachInfo?.fileWhiteList;
    if (accept || fileWhiteList) {
      const fileWhiteListArr = ((accept ?? fileWhiteList) as string)
        .split(',')
        .map((item) => (item[0] === '.' ? item : `.${item}`));
      if (!fileWhiteListArr.includes(`.${suffix}`)) {
        message.error(`不允许文件格式为${suffix}的附件上传，请重新上传`);
        return false;
      }
    }

    if (existedFileNames.includes(file.name)) {
      message.error(fileText + '文件名已存在');
      return false;
    }
    if (fileList.length + apiRef.current.attachmentRecordList?.length > maxCount) {
      message.error(`文件数量已达到上限${maxCount}个，无法上传新文件`);
      return false;
    }
    return true;
    // if (attachInfo?.maxSize && file.size / 1048576 > attachInfo?.maxSize) {
    //   message.error(fileText + '文件大小超出上限，无法上传');
    //   innerRef.current.error += 1;
    //   return false;
    // }

    // if (attachInfo?.warnSize && file.size / 1048576 > attachInfo?.warnSize) {
    //   const res = await iup.confirm(`${fileText}文件大小超过${attachInfo?.warnSize}M,确定继续上传吗`);
    //   if (!res) {
    //     innerRef.current.error += 1;
    //     return false;
    //   }
    // }
  });

  const permission: ITableAttachmentProps['permission'] = useMemo(() => {
    return {
      add: disabled ? 0 : 1,
      delete: disabled ? 0 : 1,
      edit: disabled ? 0 : 1,
      view: 1,
      preview: 1,
      download: 1,
      zipDownload: 1,
      ...propsPermission
    };
  }, [disabled, propsPermission]);

  const openTableAttachment = useRefCallback((configs: any) => {
    return openFormTableAttachment(
      {
        ...apiRef.current,
        async getCount() {
          if (innerRef.current.loadPromise) {
            const result = await innerRef.current.loadPromise;
            return result?.attachmentRecordList?.length || 0;
          }
          return apiRef.current?.attachmentRecordList?.length || 0;
        },
        getResult() {
          return innerRef.current.loadPromise || apiRef.current;
        },
        uploadingRef
      },
      configs
    );
  });

  const onContentChanged = useRefCallback(() => {
    innerRef.current.isChanged = true;
    getTableAttach(true);
  });

  const actions = useMemo(() => {
    return {
      delete: async (list: AttachmentFile[]) => {
        const ids: string[] = [];
        const tmpFileNames = list.map((v) => {
          if ((v as any).asrFid) {
            ids.push((v as any).asrFid);
          }
          return v.fileName;
        });
        if (ids.length > 0) {
          const res = await apiRef.current?.handleDelete?.(ids.join(','));
          if (res) {
            removeFiles(tmpFileNames);
            onContentChanged();
          }
          return res as boolean;
        } else {
          removeFiles(tmpFileNames);
          return true;
        }
      },
      zipDownload: () => apiRef.current.handleZipDownload?.() as Promise<void>,
      isPreview: (fileName: string) => {
        const previewFileType = apiRef.current.tableAttachInfo?.previewFileType;
        if (!previewFileType) {
          return false;
        }
        return previewFileType.includes(getFileType(fileName));
      },
      preview: (fileId: string) => apiRef.current.handlePreview?.({ asrFid: fileId }),
      download: (data: any) => apiRef.current.handleDownload?.(data),
      openTableAttachment
    };
  }, []);

  return (
    <AttachmentContext.Provider
      value={{
        mergeChunks,
        upload,
        beforeUploadValidate,
        maxCount,
        disabled,
        uploadThreadCount,
        chunkSize,
        actions,
        attachmentRecordList: attachmentRecordList || [],
        loading,
        permission,
        isReady: !!apiRef.current.asrSessionGuid,
        uploading,
        onUploadStateChange,
        attachmentFileList,
        approved,
        accept: accept || apiRef.current.tableAttachInfo?.fileWhiteList,
        onContentChanged
      }}
    >
      {children}
    </AttachmentContext.Provider>
  );
});

export const useAttachment = () => {
  const context = useContext(AttachmentContext);
  if (!context) {
    throw new Error('useAttachment must be used within a AttachmentProvider');
  }
  return context;
};
